# -*- coding: utf-8 -*- 
'''
Created on Jun 19, 2012

@author: SG0214825
'''

import os
import pprint
import random
import sys
import wx
import wx.grid
from csv_file_data_source import CSVFileDataSource

# The recommended way to use wx with mpl is with the WXAgg
# backend. 
#
import matplotlib
from matplotlib.dates import strpdate2num
from wx._core import Size
from wxPython._controls import wxScrollBar
matplotlib.use('WXAgg')
import matplotlib.dates as mdates
from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import \
    FigureCanvasWxAgg as FigCanvas, \
    NavigationToolbar2WxAgg as NavigationToolbar
import numpy as np
import pylab
        
class GraphFrame(wx.Frame):
    """ The main frame of the application
    """
    DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
    
    def __init__(self, parent, file_name = None, title = 'Wykres zużycia zasobów'):
        wx.Frame.__init__(self, parent, -1, title)
        
        if file_name is not None:
            self.data_source = CSVFileDataSource(file_name)
            self.data = self.data_source.rows
            self.lines = self.data_source.lines
        
        self.create_menu()
        self.create_status_bar()
        self.create_main_panel()
        self.draw_plot()
        

    def create_menu(self):
        self.menubar = wx.MenuBar()
        
        menu_file = wx.Menu()
        m_expt = menu_file.Append(-1, "Zapisz\tCtrl-S", "Zapisz do pliku")
        self.Bind(wx.EVT_MENU, self.on_save_plot, m_expt)
        menu_file.AppendSeparator()
        m_exit = menu_file.Append(-1, "Wyjdź\tCtrl-X", "Wyjdź")
        self.Bind(wx.EVT_MENU, self.on_exit, m_exit)
                
        self.menubar.Append(menu_file, "Plik")
        self.SetMenuBar(self.menubar)

    def create_main_panel(self):
        self.panel = wx.Panel(self)

        self.init_plot()
        self.canvas = FigCanvas(self.panel, -1, self.fig)
        self.canvas.SetMinSize(wx.Size( 500,500 ))
        
        self.cb_grid = wx.CheckBox(self.panel, -1, 
            "Pokaż siatkę",
            style=wx.ALIGN_RIGHT)
        self.Bind(wx.EVT_CHECKBOX, self.on_cb_grid, self.cb_grid)
        self.cb_grid.SetValue(True)
        
        self.cb_xlab = wx.CheckBox(self.panel, -1, 
            "Pokaż oś czasu",
            style=wx.ALIGN_RIGHT)
        self.Bind(wx.EVT_CHECKBOX, self.on_cb_xlab, self.cb_xlab)        
        self.cb_xlab.SetValue(True)
        
        self.hbox1 = wx.BoxSizer(wx.HORIZONTAL)
        self.hbox1.Add(self.cb_grid, border=5, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL)
        self.hbox1.AddSpacer(10)
        self.hbox1.Add(self.cb_xlab, border=5, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL)
                
        self.toolbar = NavigationToolbar(self.canvas)
        
        self.vbox = wx.BoxSizer(wx.VERTICAL)
        self.vbox.Add(self.canvas, 1, flag=wx.LEFT | wx.TOP | wx.GROW) 
        self.vbox.Add(self.toolbar, 0, wx.EXPAND)       
        self.vbox.Add(self.hbox1, 0, flag=wx.ALIGN_LEFT | wx.TOP)

        self.dataGrid = DataGrid(self.panel, self.data_source.table)
        
        self.dataBox = wx.BoxSizer(wx.HORIZONTAL)        
        self.dataBox.Add(self.dataGrid, flag=wx.GROW)
        
        self.mainBox = wx.BoxSizer(wx.HORIZONTAL)
        self.mainBox.Add(self.vbox, flag=wx.LEFT | wx.GROW)
        self.mainBox.Add(self.dataBox, flag=wx.ALIGN_RIGHT)
        
        self.panel.SetSizer(self.mainBox)
        self.mainBox.Fit(self)
    
    def create_status_bar(self):
        self.statusbar = self.CreateStatusBar()

    def init_plot(self):
        self.dpi = 100
        self.fig = Figure((3.0, 3.0), dpi=self.dpi)

        self.axes = self.fig.add_subplot(111)
        self.axes.set_axis_bgcolor('white')
        self.axes.xaxis.set_major_formatter(mdates.DateFormatter(GraphFrame.DATE_FORMAT))
        
        pylab.setp(self.axes.get_xticklabels(), fontsize=6)
        pylab.setp(self.axes.get_yticklabels(), fontsize=6)


        self.axes.fmt_xdata = mdates.DateFormatter(GraphFrame.DATE_FORMAT)
        formatter = strpdate2num(GraphFrame.DATE_FORMAT)        
        self.colors = ['y', 'b', 'r', 'm']
        if not self.lines is None:
            n = 1
            for description in self.lines.keys() :
                tempMap = {}
                for key in self.lines[description].keys():
                    tempMap[formatter(key)] = self.lines[description][key]
                keys = []
                values = []
                for key in sorted(tempMap.iterkeys()):
                    keys.append(key)
                    values.append(tempMap[key])
                self.axes.xaxis_date()
                self.axes.plot(
                    keys,
                    values,
                    linewidth=1,
                    color=self.colors[n-1],
                    #color=(n % 2, (n % 3) % 2, (n % 2 + 1) % 2),
                    label = description,
                )
                n = n + 1
        prop = matplotlib.font_manager.FontProperties(size=7)
        self.axes.legend(loc='upper center', bbox_to_anchor=(0.5, 1.15),
          ncol=2, fancybox=True, shadow=True, prop=prop)
        self.fig.autofmt_xdate()
        
    def draw_plot(self):
        """ Redraws the plot
        """
        # when xmin is on auto, it "follows" xmax to produce a 
        # sliding window effect. therefore, xmin is assigned after
        # xmax.
        #
        timestamps = [row[0] for row in self.data_source.table]
        formatter = strpdate2num(GraphFrame.DATE_FORMAT)        
        xmin = formatter(min(timestamps))
        xmax = formatter(max(timestamps))
        #if self.xmax_control.is_auto():
        self.xmax = xmax
#        else:
#            self.xmax = xmin+((xmax-xmin)*int(self.xmax_control.manual_value())/100)
#        if self.xmin_control.is_auto():            
        self.xmin = xmin
#        else:
#            self.xmin = xmin+((xmax-xmin)*float(self.xmin_control.manual_value())/100.0)
#        if self.xmin<1:
#            self.xmin = xmin

        # for ymin and ymax, find the minimal and maximal values
        # in the data set and add a mininal margin.
        # 
        # note that it's easy to change this scheme to the 
        # minimal/maximal value in the current display, and not
        # the whole data set.
        # 
    #        ymin = 0
    #        ymax = 100
        #if self.ymin_control.is_auto():
        self.ymin = round(min(self.data), 0) - 1
#        else:
#            self.ymin = int(self.ymin_control.manual_value())
        
        #if self.ymax_control.is_auto():
        self.ymax = round(max(self.data), 0) + 1
#        else:
#            self.ymax = int(self.ymax_control.manual_value())
            
        self.draw_plot_with_values(self.xmin, self.xmax, self.ymin, self.ymax)

    def draw_plot_with_values(self, xmin, xmax, ymin, ymax):

        #print "plot",xmin, xmax, ymin, ymax
        self.axes.set_xbound(lower=xmin, upper=xmax)
        self.axes.set_ybound(lower=ymin, upper=ymax) 
        
        # anecdote: axes.grid assumes b=True if any other flag is
        # given even if b is set to False.
        # so just passing the flag into the first statement won't
        # work.
        #
        if self.cb_grid.IsChecked():
            self.axes.grid(True, color='gray')
        else:
            self.axes.grid(False)

        # Using setp here is convenient, because get_xticklabels
        # returns a list over which one needs to explicitly 
        # iterate, and setp already handles this.
        #  
        pylab.setp(self.axes.get_xticklabels(), 
            visible=self.cb_xlab.IsChecked())
        
        #self.plot_data.set_xdata(np.arange(len(self.data)))
        #self.plot_data.set_ydata(np.array(self.data))

        self.canvas.draw()

        back_formatter = mdates.DateFormatter(GraphFrame.DATE_FORMAT)
        xmin = back_formatter(xmin)
        xmax = back_formatter(xmax)
        self.dataGrid.repaint(xmin, xmax, ymin, ymax)
        
    def on_click_down(self, event):
        self.last_down_x = event.X;
        self.last_down_y = event.Y;

    def on_click_up(self, event):
        canvas_size = self.canvas.GetSize()
        fig_width = self.fig.get_figwidth() * self.fig.get_dpi()
        fig_height = self.fig.get_figheight() * self.fig.get_dpi()
        start_x = (canvas_size.width - fig_width) / 2 
        end_x = fig_width + (canvas_size.width - fig_width) / 2
        start_y = (canvas_size.height - fig_height) / 2
        end_y = fig_height + (canvas_size.height - fig_height) / 2
        top = int(fig_height*0.1)
        bottom = int(fig_height*0.8)
        left = int(fig_width*0.12)
        right = int(fig_width*0.9)
        print event.X, event.Y
        if event.X>left and event.X<right and self.last_down_x>left and self.last_down_x<right and event.Y>top and event.Y<bottom and self.last_down_y>top and self.last_down_y<bottom:
            xmin=self.xmin+((self.xmax-self.xmin)*(float(min(event.X, self.last_down_x))-left)/(right-left))
            xmax=self.xmin+((self.xmax-self.xmin)*(float(max(event.X, self.last_down_x))-left)/(right-left))
                    
            ymin=float(min(bottom - event.Y, bottom - self.last_down_y))/(bottom-top)*(self.ymax-self.ymin)+self.ymin
            ymax=float(max(bottom - event.Y, bottom - self.last_down_y))/(bottom-top)*(self.ymax-self.ymin)+self.ymin
    
            self.draw_plot_with_values(xmin, xmax, ymin, ymax)
            
    def on_cb_grid(self, event):
        self.draw_plot()
    
    def on_cb_xlab(self, event):
        self.draw_plot()
    
    def on_save_plot(self, event):
        file_choices = "PNG (*.png)|*.png"
        
        dlg = wx.FileDialog(
            self, 
            message="Save plot as...",
            defaultDir=os.getcwd(),
            defaultFile="plot.png",
            wildcard=file_choices,
            style=wx.SAVE)
        
        if dlg.ShowModal() == wx.ID_OK:
            path = dlg.GetPath()
            self.canvas.print_figure(path, dpi=self.dpi)
            self.flash_status_message("Saved to %s" % path)
    
    def on_redraw_timer(self, event):
        # if paused do not add data, but still redraw the plot
        # (to respond to scale modifications, grid change, etc.)
        #
        if not self.paused:
            self.data.append(self.datagen.next())
        
        self.draw_plot()
    
    def on_exit(self, event):
        self.Destroy()
    
class DataGrid(wx.grid.Grid):
    def __init__(self, parent, table):
        wx.grid.Grid.__init__(self, parent, -1)
        self.data = table
        self.ymin = 0
        self.ymax = 100
        timestamps = [row[0] for row in self.data]
        self.xmin = min(timestamps)
        self.xmax = max(timestamps)
#        data = (("A", "B"), 
#                ("C", "D"), 
#                ("E", "F"), 
#                ("G", "G"),
#                ("F", "F"), 
#                ("Q", "Q"))
        self.colLabels = ("Timestamp", "Resource", "Value", "User", "Process")
        self.repaintGrid()
        
    def repaintGrid(self):
        self.repaint(self.xmin, self.xmax, self.ymin, self.ymax)
        self.addScrolls()
        
    def addScrolls(self):
        # The scroll bars aren't resized (at least on windows)
        # Jiggling the size of the window rescales the scrollbars
        h,w = self.GetSize()
        self.SetSize((h+1, w))
        self.SetSize((h, w))
        self.ForceRefresh()    
        
    def repaint(self, xmin, xmax, ymin, ymax):
        table = filter(lambda row : ymin <= row[2] <= ymax and xmin <= row[0] <= xmax, self.data)
    #print "grid",xmin, xmax, ymin, ymax
        rowLabels = range(len(table))
        rowLabels = tuple(map(lambda x : str(x), rowLabels))
        tableBase = GenericTable(table, rowLabels, self.colLabels)
        self.SetScrollbar(wx.VERTICAL, 10, 10, 10, True)   
        self.SetTable(tableBase)
        self.AutoSizeColumns()
        self.addScrolls()
        
class GenericTable(wx.grid.PyGridTableBase):
    def __init__(self, data, rowLabels=None, colLabels=None):
        wx.grid.PyGridTableBase.__init__(self)
        self.data = data
        self.rowLabels = rowLabels
        self.colLabels = colLabels
        
    def GetNumberRows(self):
        return len(self.data)

    def GetNumberCols(self):
        return len(self.data[0])

    def GetColLabelValue(self, col):
        if self.colLabels:
            return self.colLabels[col]
            
    def GetRowLabelValue(self, row):
        if self.rowLabels:
            return self.rowLabels[row]
            
    def IsEmptyCell(self, row, col):
        return False

    def GetValue(self, row, col):
        return self.data[row][col]

    def SetValue(self, row, col, value):
        pass
    
        
if __name__ == '__main__':
    app = wx.PySimpleApp()
    app.frame = GraphFrame(None, '../../../test_data/chromi.log')
    app.frame.Show()
    app.MainLoop()